﻿using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

namespace GamePlayConfig
{
    public static class ConfigUtility
    {
        public const string HEADER_SUFFIX = "_hd";
        public const string CFG_SUFFIX = "Cfg";
        
        
        /// <summary>
        /// 加载 TConfig 的全部配置
        /// </summary>
        public static void Load<TConfig>(Dictionary<int, TConfig> cache) where TConfig : Config<TConfig>, IBinaryDeserializable, new()
        {
            string binName = typeof(TConfig).Name.Replace(CFG_SUFFIX,string.Empty);

            try
            {
                using var stream = File.Open(Path.Combine(Application.dataPath, $"GamePlayConfig/Output/{binName}.bin"), FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(stream);

                while (reader.BaseStream.Position < reader.BaseStream.Length)
                {
                    var instance = new TConfig();
                    instance.Deserialize(reader);
                    cache.Add(instance.ID, instance);
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }
        }
        
        
        /// <summary>
        /// 加载指定位置的TConfig配置
        /// </summary>
        public static TConfig LoadAt<TConfig>(long offset) where TConfig : Config<TConfig>, IBinaryDeserializable, new()
        {
            if (offset < 0) return null;
            
            string binName = typeof(TConfig).Name.Replace(CFG_SUFFIX,string.Empty);
            string binFilePath = Path.Combine(Application.dataPath, $"GamePlayConfig/Output/{binName}.bin");

            if (!File.Exists(binFilePath))
            {
                Debug.LogError($"File not found: {binFilePath}");
                return null;
            }
            
            using var binStream = File.Open(binFilePath, FileMode.Open, FileAccess.Read);
            using var binReader = new BinaryReader(binStream);

            
            binReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            var instance = new TConfig();
            instance.Deserialize(binReader);
            return instance;
        }

        /// <summary>
        /// 加载TConfig所有的id
        /// </summary>
        public static void LoadKeys<TConfig>(ref List<int> keys) where TConfig : Config<TConfig>, IBinaryDeserializable, new()
        {
            string binName = typeof(TConfig).Name.Replace(CFG_SUFFIX, HEADER_SUFFIX);
            string filePath = Path.Combine(Application.dataPath, $"GamePlayConfig/Output/{binName}.bin");
            try
            {
                if (!File.Exists(filePath) )
                {
                    Debug.LogError($"File not found: {filePath}");
                    return;
                }
                using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(stream);
                
                while (reader.BaseStream.Position < reader.BaseStream.Length)
                {
                    keys.Add(reader.ReadInt32());
                    reader.BaseStream.Seek(sizeof(long), SeekOrigin.Current);
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }
        }
    }

    public abstract class Config<TConfig> where TConfig : Config<TConfig>, IBinaryDeserializable, new()
    {
        private static List<(int id, long offset)> m_Offsets = null;
        private static List<int> m_keys = null;
        private static readonly Dictionary<int,TConfig> m_Configs = new Dictionary<int, TConfig>();

        /// <summary>
        /// 单条数据的唯一标识符
        /// </summary>
        public int ID { get; protected set; }
        
        /// <summary>
        /// 从bin文件加载全部配置数据
        /// </summary>
        public static void LoadAll()
        {
            ConfigUtility.Load<TConfig>(m_Configs);
        }

        /// <summary>
        /// 加载Header中的信息
        /// </summary>
        private static void LoadHeader()
        {
            string binName = typeof(TConfig).Name.Replace(ConfigUtility.CFG_SUFFIX, ConfigUtility.HEADER_SUFFIX);
            string filePath = Path.Combine(Application.dataPath, $"GamePlayConfig/Output/{binName}.bin");
            try
            {
                if (!File.Exists(filePath) )
                {
                    Debug.LogError($"File not found: {filePath}");
                    return;
                }
                using var stream = File.Open(filePath, FileMode.Open, FileAccess.Read);
                using var reader = new BinaryReader(stream);
                
                m_keys = new List<int>();
                m_Offsets = new List<(int id, long offset)>();
                while (reader.BaseStream.Position < reader.BaseStream.Length)
                {
                    int id = reader.ReadInt32();
                    long offset = reader.ReadInt64();
                    m_keys.Add(id);
                    m_Offsets.Add((id, offset));
                }
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }
        }

        /// <summary>
        /// 获取全部的id
        /// </summary>
        public static List<int> Keys()
        {
            if (m_keys == null)
            {
                LoadHeader();
            }
            // ConfigUtility.LoadKeys<TConfig>(ref m_keys);
            return m_keys;
        }
        
        /// <summary>
        /// 获取指定id的配置数据
        /// </summary>
        public static TConfig Get(int id)
        {
            if (!m_Configs.TryGetValue(id, out var config))
            {
                long offset = GetOffset(id);
                config = ConfigUtility.LoadAt<TConfig>(offset);
                if (config != null)
                {
                    m_Configs.Add(id, config);
                }
            }
            return config;
        }

        /// <summary>
        /// 获取id对应数据的位置
        /// </summary>
        private static long GetOffset( int lookupId)
        {
            if (m_Offsets == null)
            {
                LoadHeader();
            }

            if (m_Offsets == null)
            {
                return -1;
            }
            
            // 二分查找id对应的offset
            int left = 0;
            int right = m_Offsets.Count;
            
            while (left <= right)
            {
                int mid = left + (right - left) / 2;
                var data = m_Offsets[mid];
                if (data.id < lookupId)
                {
                    left = mid + 1;
                }
                else if (data.id > lookupId)
                {
                    right = mid - 1;
                }
                else
                {
                    return data.offset;
                }
            }

            return -1;
        }
    }
}
